// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package buildcfg

import (
	"fmt"
	"reflect"
	"strings"

	"internal/goexperiment"
)

// 实验包含为当前构建启用的工具链实验。
// 
// （这不一定是编译器本身
// 构建时使用的一组实验。）
// 
// TELTERASEBINE指定当前工具链中由
// 默认值启用的实验标志。实际上，这是“控制”
// 配置，任何与此相关的变化都是一个实验。
var Experiment, experimentBaseline = func() (goexperiment.Flags, goexperiment.Flags) {
	flags, baseline, err := ParseGOEXPERIMENT(GOOS, GOARCH, envOr("GOEXPERIMENT", defaultGOEXPERIMENT))
	if err != nil {
		Error = err
	}
	return flags, baseline
}()

const DefaultGOEXPERIMENT = defaultGOEXPERIMENT

// FramePointerEnabled允许对
// 保存帧指针使用平台约定。
// 
// 这曾经是一个实验，但现在它总是在支持它的平台上启用。
// 
// 注意：必须同意启用runtime.framepointer_。
var FramePointerEnabled = GOARCH == "amd64" || GOARCH == "arm64"

// ParseGOEXPERIMENT解析（GOOS、GOARCH、GOEXPERIMENT）
// 配置元组并返回启用和基线实验
// 标志集。
// 
// TODO（mdempsky）：移动到内部/goexperiment。
func ParseGOEXPERIMENT(goos, goarch, goexp string) (flags, baseline goexperiment.Flags, err error) {
	regabiSupported := goarch == "amd64" && (goos == "android" || goos == "linux" || goos == "darwin" || goos == "windows")

	baseline = goexperiment.Flags{
		RegabiWrappers: regabiSupported,
		RegabiG:        regabiSupported,
		RegabiReflect:  regabiSupported,
		RegabiDefer:    regabiSupported,
		RegabiArgs:     regabiSupported,
	}

	// 从静态启用的一组实验开始。
	flags = baseline

	// 从
	// 实验环境中获取对基线配置的任何更改。这可以在make.bash时设置
	// 并在生成时重写。
	if goexp != "" {
		// 创建已知实验名称的地图。
		names := make(map[string]func(bool))
		rv := reflect.ValueOf(&flags).Elem()
		rt := rv.Type()
		for i := 0; i < rt.NumField(); i++ {
			field := rv.Field(i)
			names[strings.ToLower(rt.Field(i).Name)] = field.SetBool
		}

		// “regabi”是所有工作regabi的别名，而不是实验本身。做
		// 作为别名，使“regabi”和“noregabi”
		// 做正确的事。
		names["regabi"] = func(v bool) {
			flags.RegabiWrappers = v
			flags.RegabiG = v
			flags.RegabiReflect = v
			flags.RegabiDefer = v
			flags.RegabiArgs = v
		}

		// 解析名称。
		for _, f := range strings.Split(goexp, ",") {
			if f == "" {
				continue
			}
			if f == "none" {
				// GOEXPERIMENT=none禁用所有实验标志。
				// 这是cmd/dist使用的，它不知道如何使用任何实验标志构建
				// 。
				flags = goexperiment.Flags{}
				continue
			}
			val := true
			if strings.HasPrefix(f, "no") {
				f, val = f[2:], false
			}
			set, ok := names[f]
			if !ok {
				err = fmt.Errorf("unknown GOEXPERIMENT %s", f)
				return
			}
			set(val)
		}
	}

	// regabi仅在amd64上受支持。
	if goarch != "amd64" {
		flags.RegabiWrappers = false
		flags.RegabiG = false
		flags.RegabiReflect = false
		flags.RegabiDefer = false
		flags.RegabiArgs = false
	}
	// 检查regabi依赖项。
	if flags.RegabiG && !flags.RegabiWrappers {
		err = fmt.Errorf("GOEXPERIMENT regabig requires regabiwrappers")
	}
	if flags.RegabiArgs && !(flags.RegabiWrappers && flags.RegabiG && flags.RegabiReflect && flags.RegabiDefer) {
		err = fmt.Errorf("GOEXPERIMENT regabiargs requires regabiwrappers,regabig,regabireflect,regabidefer")
	}
	return
}

// expList返回与base不同的
// 实验的小写实验名称列表。碱基可以为零，表示没有
// 实验。如果all为真，则包括所有实验标志，
// 不考虑基数。
func expList(exp, base *goexperiment.Flags, all bool) []string {
	var list []string
	rv := reflect.ValueOf(exp).Elem()
	var rBase reflect.Value
	if base != nil {
		rBase = reflect.ValueOf(base).Elem()
	}
	rt := rv.Type()
	for i := 0; i < rt.NumField(); i++ {
		name := strings.ToLower(rt.Field(i).Name)
		val := rv.Field(i).Bool()
		baseVal := false
		if base != nil {
			baseVal = rBase.Field(i).Bool()
		}
		if all || val != baseVal {
			if val {
				list = append(list, name)
			} else {
				list = append(list, "no"+name)
			}
		}
	}
	return list
}

// GOEXPERIMENT是以逗号分隔的启用或禁用的
// 实验列表，这些实验与基线实验配置不同。
// GOEXPERIMENT正是用户在命令行
// 上设置的，以获得启用的实验集。
func GOEXPERIMENT() string {
	return strings.Join(expList(&Experiment, &experimentBaseline, false), ",")
}

// EnabledExperiments返回已启用实验的列表，如
// 小写实验名称。
func EnabledExperiments() []string {
	return expList(&Experiment, nil, false)
}

// AllExperiments返回所有实验设置的列表。
// 禁用的实验出现在以“否”为前缀的列表中。
func AllExperiments() []string {
	return expList(&Experiment, nil, true)
}

// UpdateExperiments根据新的GOARCH值更新实验全局。
// 这仅适用于cmd/go，它可以在
// 由于使用“go env-w”而启动程序后更改GOARCH。
func UpdateExperiments(goos, goarch, goexperiment string) {
	var err error
	Experiment, experimentBaseline, err = ParseGOEXPERIMENT(goos, goarch, goexperiment)
	if err != nil {
		Error = err
	}
}
